Ícone Ver README do projeto

Introdução

Imagine um banco de dados de um cinema onde cada linha representa um usuário e cada coluna indica se essa pessoa assistiu ou não a um filme.



Ao analisar essa tabela, podemos identificar padrões. Como a tendência de quem assiste ao Filme A também assistir ao Filme B, e de quem assiste ao Filme C também assistir ao Filme D.

Esses padrões também são chamados de regras de associação que podem ser usadas para a criação de um sistema de recomendação. O algoritmo FP-Growth é utilizado para descobrir essas regras que nos ajudam a determinar quais filmes recomendar a um usuário com base no seu histórico de visualizações

Algoritmo FP-Growth

O FP-Growth monta uma estrutura do tipo arvore e com isso a execução dele é mais rápida que o Apriori, por exemplo.



Para explicar o funcionamento do FP-Growth, Considere os seguintes dados em que cada linha é uma transação (T1, T2,.., T5). Imagine uma transação como cada acesso de um usuário ao site MovieLens.

Cada transação tem um conjunto de itens e podemos pensar neles, como o nome dos filmes que usuário avaliou, em cada vez que acessou o site. Para simplificar, ao invés de nome dos filmes, colocaremos somente uma letra.
Por exemplo, na transação T1 o usuário avaliou os filmes {E,K,M,N,O,Y}, as outras linhas seguem a mesma lógica.

1ª Etapa

Na primeira etapa de execução do algoritmo FP-Growth, é criado uma tabela, onde a frequência de cada item individual é calculada.

Por exemplo, o item A, só ocorre 1 vez no meu conjunto de transações. Já o item K ocorre 5 vezes.

Portanto, a primeira etapa do algoritmo é a contagem de ocorrências de cada item nas transações.

2ª Etapa

Para o próximo passo, vamos considerar um suporte mínimo de 60% e uma confiança de de 80%. Como temos 5 transações, um suporte mínimo de 60% implica em 3 transações (60% ×5). Assim como confiança de 80% implica em 4 transações (80% ×5).
De posse da tabela de frequência, eu vou construir outra tabela cuja frequência dos itens seja maior ou igual ao suporte mínimo.
Neste exemplo, definimos os suportes mínimos como 3, então, só poderemos ter itens com a frequência maior ou igual a 3.


3ª Etapa

A partir da tabela criada na etapa anterior, construímos uma lista de ordem descrente de itens cuja a frequência iguala ou supera o suporte. Em seguida, criamos uma nova coluna com estes itens ordenados.

A diferença entre as colunas “Itens” e “Ordered-Item Set” é que a segunda está ordenada pelo suporte, além disso, retiramos os itens com baixo suporte.

4ª Etapa

Agora vamos iniciar a construção da arvore, pegaremos cada transação ordenada e vamos criar um “ramo” da nossa árvore cuja raiz é um valor nulo.
Cada parte do ramo tem o item seguido da sua frequência (contagem) até o momento. Primeiro, vamos inserir o conjunto da transação T1 {K,E,M,O,Y}.

Agora vamos inserir ao conjunto da transação T2 {K,E,O,Y}. Já existe um caminho para os itens K e E, então, vamos aproveitá-lo, só adicionando na contagem. Como não há um vinculo entre o caminho E e O, precisamos criar um novo nó para os itens O e Y.

Vamos inserir o conjunto da transação T3 : {K,E.M}. Nós podemos aproveitar o caminho da transação T1, só precisamos adicionar 1 elemento da contagem.

Agora, vamos inserir a transação T4 que é {K,M,Y}. Conseguimos aproveitar o nó K só adicionando 1 elemento na contagem. Em seguida, devemos criar os outros nós para M e Y.

Vamos inserir o conjunto da transação T5 : {K,E,O}. Não precisamos criar um caminho, pois ele já existe, só vamos adicionar 1 elemento em cada contagem.

Para a próxima etapa é necessário termos em mente a árvore gerada na 4ª etapa e o conjunto de itens ordenados na 3ª etapa.

5ª Etapa

Criaremos uma tabela onde será calculada a Base Condicional de Padrões (Conditional Pattern Base). Esta base é composta pelas transações que possuem o item mencionado na 1ª coluna (Itens). A 1ª coluna (Itens) está organizada em ordem crescente de suporte.

Vamos criar a tabela em verde, a partir dos resultados gerados nas etapas anteriores.

Por exemplo, a transação T1 tem os itens {K,E,M,O,Y}, ela vai entrar na base condicional de padrões para os itens Y. Para descobrir quais itens estão relacionados com o Y, precisamos olhar o caminho da arvore. Então, adicionamos o conjunto de itens do caminho e o suporte do Y.
Outro exemplo, seria o item O. Ele está presente nas transações T1,T2,T5. Então, eu olho os caminhos que o item O percorreu e seu suporte.
Os demais itens seguem esta regra.

6ª Etapa

Agora eu pego a base condicional de padrões e verifico em cada linha quais itens se repetem e também vejo qual é a soma dos suportes individuais.
Por exemplo, na linha do item O, o K e o E se repetem nos dois conjuntos. Então, adicionamos estes dois itens na coluna de Árvore de Padrões Frequentes (Conditional Frequent Pattern Tree).Além disso, a soma dos suportes de K e E é 3.
Para o item K, nenhum elemento foi gerado na etapa anterior, então, ambas colunas ficam vazias.

7ª Etapa

Nesta etapa, pegaremos a coluna de itens e a coluna Árvore de Padrões Frequentes e faremos uma combinação e vamos gerar a coluna Frequent Pattern Generated.
Por exemplo, na segunda linha temos o item O e o conjunto {K,E:3}. Podemos combiná-los da seguinte forma:

8ª Etapa

Uma vez que eu tenho a coluna Frequent Pattern Generated (Padrões Frequentes Gerados), nós podemos gerar as regras de associação.
Para cada linha, dois tipos de regras de associação podem ser inferidos. Por exemplo, na primeira linha nós podemos ter K → Y ou Y → K. Para determinar a regra válida, a confiança é calculada e a regra com maior confiança maior ou igual ao valor mínimo de confiança é mantida.

Nós temos 3 transações em que os itens K e Y ocorrem. Vamos descobrir qual das duas regras será a escolhida, K → Y ou Y → K:

K → Y significa que quem assiste o Filme K também assiste o filme Y. Neste caso, temos uma confiança de 60%.

$\large\text{c}= \large \frac{\text{Transações que os itens}\ \textbf{K} \ \text{e} \ \textbf{Y}\ \text{ocorrem}}{\text{Total de transações que tem o}\ \textbf{K}} = \frac{3}{5}=0,6$

$\small\text{Sendo,}$
$\small\text{c = confiança}$

Y → K significa que quem assiste o Filme Y também assiste o filme K. A confiança é de 100%.

$\large\text{c}= \large \frac{\text{Transações que os itens}\ \textbf{Y} \ \text{e} \ \textbf{K}\ \text{ocorrem}}{\text{Total de transações que tem o}\ \textbf{K}} = \frac{3}{3}=1$

A confiança de Y → K é maior que a de K → Y. Portanto, a regra escolhida é Y → K. A regras para os demais elementos seguem a mesma lógica.

%load_ext pretty_jupyter

Importar Bibliotecas

import pandas as pd
import os
import numpy as np
from mlxtend.frequent_patterns import fpgrowth
from mlxtend.frequent_patterns import association_rules

Definir Diretório

os.chdir("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2")

Carregar Arquivos

Tabelas de apoio

genero_user_treino1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/genero_user_treino1.pickle", compression='gzip')
genero_user_teste1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/genero_user_teste1.pickle", compression='gzip')
genero_user_treino1
movieId title genres Ano_do_filme titulo_sem_ano genres_separado userId Numero_de_Avaliacoes_por_usuarios Numero_de_Avaliacoes_por_Filme rating timestamp rating_times rating_medio_simples rating_medio_ponderado
0 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy 1995 Toy Story Adventure 144 154 1848 3.5 2014-01-12 03:50:37 0.515954 3.928842 3.264437
1 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy 1995 Toy Story Adventure 304 498 1848 3.0 2023-06-01 19:24:35 2.453737 3.928842 3.264437
2 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy 1995 Toy Story Adventure 461 1692 1848 4.5 2020-08-28 20:04:03 2.225713 3.928842 3.264437
3 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy 1995 Toy Story Adventure 751 469 1848 3.0 2012-09-30 20:05:17 0.349977 3.928842 3.264437
4 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy 1995 Toy Story Adventure 974 1160 1848 5.0 1999-09-17 11:01:24 0.053903 3.928842 3.264437
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
820503 288557 Initial D: Third Stage (2001) Action|Animation|Romance 2001 Initial D: Third Stage Action 256559 803 2 4.0 2023-06-30 15:36:23 3.319434 4.000000 0.140522
820504 288647 Everybody's Oma (2022) Documentary 2022 Everybody's Oma Documentary 218862 886 1 4.5 2023-07-05 08:02:09 3.743711 4.500000 0.126201
820505 288669 Insidious: The Red Door (2023) Horror|Mystery|Thriller 2023 Insidious: The Red Door Horror 324508 1126 1 2.5 2023-07-15 02:49:04 2.090265 2.500000 0.070112
820506 288679 The Out-Laws (2023) Action|Comedy|Romance 2023 The Out-Laws Action 65065 2569 1 2.5 2023-07-09 01:00:03 2.084003 2.500000 0.070112
820507 288761 Novalis - Die blaue Blume (1993) Drama 1993 Novalis - Die blaue Blume Drama 222466 571 1 4.0 2023-07-11 09:29:45 3.337741 4.000000 0.112179

820508 rows × 14 columns

Tabelas para rodar o algoritmo

recomendacao_genero_treino1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/recomendacao_genero_treino1.pickle", compression='gzip')
recomendacao_genero_teste1 = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.3_Datasets_Transformação_parte_3/recomendacao_genero_teste1.pickle", compression='gzip')
recomendacao_genero_treino1
userId (no genres listed) Action Adventure Animation Children Comedy Crime Documentary Drama ... Film-Noir Horror IMAX Musical Mystery Romance Sci-Fi Thriller War Western
0 5 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 1 0 0 1 0 0
1 5 0 0 0 0 0 0 0 0 1 ... 0 0 0 0 0 0 0 0 0 0
2 5 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 1 0 0 1 0 0
3 5 0 0 0 0 0 0 1 0 1 ... 0 0 0 0 0 0 0 0 0 0
4 5 0 0 0 0 0 1 0 0 1 ... 0 0 0 0 0 0 0 1 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
820503 330963 0 0 0 0 0 0 0 0 1 ... 0 1 0 0 0 0 0 1 0 0
820504 330963 0 0 0 0 0 0 0 0 1 ... 0 0 0 1 0 1 0 0 0 0
820505 330963 0 0 0 0 0 0 0 0 1 ... 0 0 0 0 0 0 0 0 0 0
820506 330963 0 0 0 0 0 0 0 0 0 ... 0 1 0 0 0 0 0 1 0 0
820507 330963 0 0 1 0 0 0 0 0 0 ... 0 0 0 0 0 1 0 1 0 0

820508 rows × 21 columns

recomendacao_genero_teste1
userId (no genres listed) Action Adventure Animation Children Comedy Crime Documentary Drama ... Film-Noir Horror IMAX Musical Mystery Romance Sci-Fi Thriller War Western
0 128 0 1 0 0 0 0 0 0 1 ... 0 0 0 0 0 1 0 0 0 0
1 128 0 1 1 0 0 0 0 0 0 ... 0 0 0 0 0 0 1 0 0 0
2 128 0 0 0 0 0 1 0 0 1 ... 0 0 0 0 0 1 0 0 1 0
3 128 0 1 1 0 0 0 0 0 0 ... 0 0 0 0 0 0 1 1 0 0
4 128 0 0 1 0 0 0 0 0 1 ... 0 0 0 0 0 0 0 0 0 1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
207989 330948 0 1 0 0 0 0 0 0 1 ... 0 0 0 0 0 0 0 0 1 0
207990 330948 0 1 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 1 0 0 0
207991 330948 0 1 0 0 0 0 1 0 0 ... 0 0 0 0 0 0 0 1 0 0
207992 330948 0 0 0 0 1 1 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
207993 330948 0 0 0 0 0 1 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

207994 rows × 21 columns

Modelagem

Tentamos criar um modelo com regras de associação de filmes, porém, não foi possível devido ao tamanho do dataset. Como alternativa, fizemos um modelo para recomendação de gênero. Em seguida, vamos aplicar um filtro e obter os filmes com as maiores nota de cada gênero.

Preparar os dados

userId está como coluna, mas precisamos que ele seja index.

Verificar o tipo de dados

# Verificar o tipo de dados na tabela de treino
recomendacao_genero_treino1.dtypes
userId                int64
(no genres listed)    int64
Action                int64
Adventure             int64
Animation             int64
Children              int64
Comedy                int64
Crime                 int64
Documentary           int64
Drama                 int64
Fantasy               int64
Film-Noir             int64
Horror                int64
IMAX                  int64
Musical               int64
Mystery               int64
Romance               int64
Sci-Fi                int64
Thriller              int64
War                   int64
Western               int64
dtype: object

Para economizar memória vamos transformar os dados de int64 para int8.

# Transformar as dummies em int8
recomendacao_genero_treino1= recomendacao_genero_treino1.astype(np.int8)
recomendacao_genero_teste1= recomendacao_genero_teste1.astype(np.int8)

Tranformar coluna em index

#Transformar 'userId' em índice
recomendacao_genero_treino1.set_index('userId', inplace=True)
recomendacao_genero_teste1.set_index('userId', inplace=True)
# Verificar se todos os valores são 0 ou 1 (verificar se a transformação int8 deu certo)
valores_binarios_treino = recomendacao_genero_treino1.apply(lambda x: x.isin([0, 1]).all()).all()
print("O DataFrame de treino contém apenas valores binários (0 ou 1)?", valores_binarios)
O DataFrame de treino contém apenas valores binários (0 ou 1)? True
# Verificar se todos os valores são 0 ou 1 (verificar se a transformação int8 deu certo)
valores_binarios_teste = recomendacao_genero_teste1.apply(lambda x: x.isin([0, 1]).all()).all()
print("O DataFrame de teste contém apenas valores binários (0 ou 1)?", valores_binarios)
O DataFrame de teste contém apenas valores binários (0 ou 1)? True
recomendacao_genero_treino1
(no genres listed) Action Adventure Animation Children Comedy Crime Documentary Drama Fantasy Film-Noir Horror IMAX Musical Mystery Romance Sci-Fi Thriller War Western
userId
5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
5 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
5 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
-45 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0
-45 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 1 0 0 0 0
-45 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
-45 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0
-45 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0

820508 rows × 20 columns

recomendacao_genero_teste1
(no genres listed) Action Adventure Animation Children Comedy Crime Documentary Drama Fantasy Film-Noir Horror IMAX Musical Mystery Romance Sci-Fi Thriller War Western
userId
-128 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0
-128 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
-128 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 1 0 0 1 0
-128 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
-128 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
-60 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0
-60 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
-60 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0
-60 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-60 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0

207994 rows × 20 columns

Aplicar o algoritmo e Gerar Regras de Associação

# Aplicar algoritmo nos dados de treino
frequent_items_fp = fpgrowth(recomendacao_genero_treino1, min_support=0.01, use_colnames=True)
print("Itens frequentes encontrados:")
print(frequent_items_fp.head())
Itens frequentes encontrados:
    support    itemsets
0  0.270196  (Thriller)
1  0.080949   (Mystery)
2  0.436996     (Drama)
3  0.167842     (Crime)
4  0.352519    (Comedy)
# Gerar regras de associação
rules_fp = association_rules(frequent_items_fp, metric="lift", min_threshold=0.8)
rules_fp
antecedents consequents antecedent support consequent support support confidence lift leverage conviction zhangs_metric
0 (Thriller) (Drama) 0.270196 0.436996 0.105986 0.392254 0.897615 -0.012089 0.926380 -0.135168
1 (Drama) (Thriller) 0.436996 0.270196 0.105986 0.242532 0.897615 -0.012089 0.963478 -0.168467
2 (Thriller) (Action) 0.270196 0.301245 0.130563 0.483216 1.604062 0.049168 1.352122 0.516005
3 (Action) (Thriller) 0.301245 0.270196 0.130563 0.433411 1.604062 0.049168 1.288067 0.538934
4 (Thriller, Drama) (Action) 0.105986 0.301245 0.031022 0.292703 0.971643 -0.000905 0.987922 -0.031613
... ... ... ... ... ... ... ... ... ... ...
455 (Adventure) (Animation, Comedy, Fantasy, Children) 0.238032 0.012210 0.011575 0.048626 3.982634 0.008668 1.038278 0.982862
456 (Children) (Fantasy, Comedy, Adventure, Animation) 0.086418 0.012344 0.011575 0.133936 10.850667 0.010508 1.140397 0.993715
457 (Animation) (Fantasy, Comedy, Adventure, Children) 0.068840 0.014320 0.011575 0.168136 11.741023 0.010589 1.184905 0.982461
458 (Fantasy) (Animation, Comedy, Adventure, Children) 0.114785 0.023537 0.011575 0.100837 4.284243 0.008873 1.085969 0.865989
459 (Comedy) (Animation, Adventure, Fantasy, Children) 0.352519 0.014998 0.011575 0.032834 2.189205 0.006287 1.018441 0.838965

460 rows × 10 columns

Associar o id do cliente ao resultado do FP-Growth

⚠ 📌 Ao associar os resultados com o userId, notamos que nem todos os clientes possuem recomendações.

Função que associa userId e recomendações de gênero

def apply_association_rules(user_data, rules)->pd.DataFrame():
    ''' Função que associa userId e recomendações de gênero.
        Esta função é utilizada após a execução do algoritmo FP_Growth() 
        e a criação das regras de associação.

    Args:
    - user_data: Um conjunto de dados contendo informações sobre os usuários e seus filmes assistidos.
    - rules: Um conjunto de regras de associação geradas pelo algoritmo FP Growth, 
            que indicam relações frequentes entre gêneros de filmes.

    Return:
    - A função retorna um pd.DataFrame() com as recomendações para cada usuário 
      com base nas regras de associação.
    '''
    recommendations = []
    
    # Iterar user_data agrupado por userId
    for user_id, data in user_data.groupby('userId'):
        user_preferences = set(data['genres'])  
        user_recommendations = []
        
        used_genres = set()  # Conjunto para controlar gêneros já utilizados
        
        for idx, rule in rules.iterrows():
            antecedents = set(rule['antecedents'])
            consequents = set(rule['consequents'])
            
            if antecedents.issubset(user_preferences):
                for item in consequents:
                    if item not in user_preferences and item not in used_genres:
                        user_recommendations.append(item)
                        used_genres.add(item)  # Marca o gênero como utilizado
            
        
        # Preencher com None se tiver menos de 2 recomendações
        while len(user_recommendations) < 3:
            user_recommendations.append(None)
        
        recommendations.append({'userId': user_id,
                                'Recomendacao_genero_1': user_recommendations[0],
                                'Recomendacao_genero_2': user_recommendations[1],
                                'Recomendacao_genero_3': user_recommendations[2]})

    recommendations_df = pd.DataFrame(recommendations)
    return recommendations_df
# Filtrar apenas regras com um lift alto, por exemplo
rules_high_lift = rules_fp[rules_fp['lift'] > 1.5]

# Aplicar regras de associação aos dados de teste
user_recomendacoes_teste1 = apply_association_rules(genero_user_teste1 , rules_high_lift)
# Tabela com as recomendações de gênero
user_recomendacoes_teste1
userId Recomendacao_genero_1 Recomendacao_genero_2 Recomendacao_genero_3
0 128 War Action None
1 172 Action Mystery Crime
2 465 Romance Adventure War
3 598 Action Mystery Crime
4 919 Romance Adventure War
... ... ... ... ...
1981 330236 Romance Adventure War
1982 330321 Action Mystery Crime
1983 330496 None None None
1984 330667 Action Mystery Crime
1985 330948 Thriller Crime Romance

1986 rows × 4 columns

# Contagens de gêneros recomendados em primeiro lugar
user_recomendacoes_teste1['Recomendacao_genero_1'].value_counts().to_frame()
count
Recomendacao_genero_1
Romance 767
Action 480
War 263
Mystery 118
Thriller 66
Adventure 18
Drama 8
Crime 3
Comedy 3
Musical 2
Children 1
Fantasy 1
# Verificar quantos valores None existem em cada coluna
none_counts = user_recomendacoes_teste1.isna().sum()

# Exibir os resultados
print("Quantidade de None por coluna:")
print(none_counts)
Quantidade de None por coluna:
userId                     0
Recomendacao_genero_1    256
Recomendacao_genero_2    256
Recomendacao_genero_3    487
dtype: int64

Modelo 1: .loc[]

Abaixo, vamos filtrar os filmes com os maiores rating_medio_ponderado por gênero e associá-los aos resultados encontrados acima.

Filtrar os filmes por gênero

A função a seguir filtra os 2 filmes com os maiores rating_medio_ponderado por gênero.

def filtrar_filmes_por_genero(dataframe, generos_unicos):
    # Inicializar um dicionário para armazenar os DataFrames filtrados por gênero
    filmes_por_genero = {}

    # Iterar sobre cada gênero único
    for genero in generos_unicos:
        # Filtrar os filmes por gênero
        filmes_do_genero = dataframe[dataframe['genres_separado'] == genero]
        
        # Ordenar filmes_do_genero pela rating_medio_ponderado em ordem decrescente
        filmes_do_genero = filmes_do_genero.sort_values(by='rating_medio_ponderado', ascending=False)

        # Remover duplicatas de filmes baseado no título, mantendo apenas o primeiro (com a maior pontuação)
        filmes_do_genero = filmes_do_genero.drop_duplicates(subset='title')

        # Selecionar os top 2 filmes com as maiores pontuações
        top_2_filmes = filmes_do_genero.head(2)

        # Adicionar ao dicionário com a chave sendo o nome do gênero
        filmes_por_genero[genero] = top_2_filmes

    # Retornar o dicionário de DataFrames filtrados por gênero
    return filmes_por_genero
# Lista dos gêneros do dataset
lista_generos = list(genero_user_treino1['genres_separado'].unique())
print(lista_generos)
['Adventure', 'Comedy', 'Action', 'Drama', 'Crime', 'Children', 'Mystery', 'Documentary', 'Animation', 'Thriller', 'Horror', 'Fantasy', 'Western', 'Film-Noir', 'Romance', 'Sci-Fi', 'Musical', 'War', '(no genres listed)']

Agora vamos executar a função e para ver se está funcionando, vamos utilizá-la para filtrar os filmes de ação com os maiores rating_medio_ponderado.

# Filtrar os filmes por gênero e armazenar em um dicionário
filmes_por_genero = filtrar_filmes_por_genero(genero_user_treino1, lista_generos )

# Filtrar os filmes de ação
genero_acao = filmes_por_genero['Action']
genero_acao
movieId title genres Ano_do_filme titulo_sem_ano genres_separado userId Numero_de_Avaliacoes_por_usuarios Numero_de_Avaliacoes_por_Filme rating timestamp rating_times rating_medio_simples rating_medio_ponderado
352621 2571 Matrix, The (1999) Action|Sci-Fi|Thriller 1999 Matrix, The Action 192468 33 2578 2.5 2016-05-21 11:10:06 0.566539 4.183670 3.630555
388469 2959 Fight Club (1999) Action|Crime|Drama|Thriller 1999 Fight Club Action 110663 89 2049 4.0 2018-02-10 22:32:29 1.242710 4.209614 3.549180

Associar gênero, filme e userId

Vamos unir o resultado do fp_growth() que recomendou gêneros e a filtragem dos 2 filmes com os maiores rating_medio_ponderado por gênero.

# Passo 1: Obter recomendações de filmes por gênero usando a função filtrar_filmes_por_genero
filmes_por_genero_teste = filtrar_filmes_por_genero(genero_user_teste1, lista_generos)


# Passo 2: Criar uma lista para armazenar as recomendações finais
recomendacoes_finais = []

# Passo 3: Iterar sobre cada userId e suas recomendações de gênero e filmes
for user_id, recomendacoes_genero in user_recomendacoes_teste1.iterrows():
    user_id = recomendacoes_genero['userId']  # Extraindo o userId corretamente
    # Inicializar as recomendações de filmes para este userId
    recomendacoes_filmes = {
        'userId': user_id,
        'Recomendacao_genero_1': recomendacoes_genero['Recomendacao_genero_1'],
        'Recomendacao_genero_2': recomendacoes_genero['Recomendacao_genero_2'],
        'Recomendacao_genero_3': recomendacoes_genero['Recomendacao_genero_3'],
        'Recomendacao_filme_1': None,
        'Recomendacao_filme_2': None,
        'Recomendacao_filme_3': None,
        'Recomendacao_filme_4': None,
        'Recomendacao_filme_5': None,

    }
    
    # Contador para acompanhar o número de recomendações preenchidas
    count_recomendacoes = 0
    
    # Iterar sobre as recomendações de gênero para este userId
    for coluna_genero, genero_recomendado in recomendacoes_genero.items():
        # Verificar se há um gênero recomendado
        if pd.notna(genero_recomendado) and genero_recomendado in filmes_por_genero_teste:
            # Pegar os top 2 filmes para este gênero
            filmes_genero = filmes_por_genero_teste[genero_recomendado].head(2)['title'].tolist()
            
            # Preencher as recomendações de filmes vazias na ordem até o máximo de 3
            for i, filme in enumerate(filmes_genero):
                if count_recomendacoes < 5:
                    if pd.isna(recomendacoes_filmes[f'Recomendacao_filme_{count_recomendacoes + 1}']):
                        recomendacoes_filmes[f'Recomendacao_filme_{count_recomendacoes + 1}'] = filme
                        count_recomendacoes += 1
    
    # Adicionar as recomendações deste userId à lista final
    recomendacoes_finais.append(recomendacoes_filmes)

# Passo 4: Criar o DataFrame final com as recomendações de filmes e gêneros
recomendacoes_finais_df = pd.DataFrame(recomendacoes_finais)
# Tabela com as recomendações 
recomendacoes_finais_df
userId Recomendacao_genero_1 Recomendacao_genero_2 Recomendacao_genero_3 Recomendacao_filme_1 Recomendacao_filme_2 Recomendacao_filme_3 Recomendacao_filme_4 Recomendacao_filme_5
0 128 War Action None Run Silent Run Deep (1958) Greyhound (2020) Matrix, The (1999) Fight Club (1999) None
1 172 Action Mystery Crime Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) Twelve Monkeys (a.k.a. 12 Monkeys) (1995) Shawshank Redemption, The (1994)
2 465 Romance Adventure War Adjustment Bureau, The (2011) Meet Joe Black (1998) Lord of the Rings: The Fellowship of the Ring,... Lord of the Rings: The Two Towers, The (2002) Run Silent Run Deep (1958)
3 598 Action Mystery Crime Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) Twelve Monkeys (a.k.a. 12 Monkeys) (1995) Shawshank Redemption, The (1994)
4 919 Romance Adventure War Adjustment Bureau, The (2011) Meet Joe Black (1998) Lord of the Rings: The Fellowship of the Ring,... Lord of the Rings: The Two Towers, The (2002) Run Silent Run Deep (1958)
... ... ... ... ... ... ... ... ... ...
1981 330236 Romance Adventure War Adjustment Bureau, The (2011) Meet Joe Black (1998) Lord of the Rings: The Fellowship of the Ring,... Lord of the Rings: The Two Towers, The (2002) Run Silent Run Deep (1958)
1982 330321 Action Mystery Crime Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) Twelve Monkeys (a.k.a. 12 Monkeys) (1995) Shawshank Redemption, The (1994)
1983 330496 None None None None None None None None
1984 330667 Action Mystery Crime Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) Twelve Monkeys (a.k.a. 12 Monkeys) (1995) Shawshank Redemption, The (1994)
1985 330948 Thriller Crime Romance Fugitive, The (1993) Spotlight (2015) Shawshank Redemption, The (1994) Silence of the Lambs, The (1991) Adjustment Bureau, The (2011)

1986 rows × 9 columns

# Verificar a quantidade de NA (falta de recomendação)
recomendacoes_finais_df.isna().sum()
userId                     0
Recomendacao_genero_1    256
Recomendacao_genero_2    256
Recomendacao_genero_3    487
Recomendacao_filme_1     256
Recomendacao_filme_2     256
Recomendacao_filme_3     257
Recomendacao_filme_4     257
Recomendacao_filme_5     487
dtype: int64

Avaliar

Para avaliarmos vamos criar uma coluna dummy em que:

  • 0 significa que nenhum filme da recomendação foi assistido pelo usuário ;
  • 1 significa que pelo menos um dos filmes recomendados foi assistido pelo usuário.
# Função para criar a coluna dummy
def assistiu_ou_nao(row, df_teste):
    user_id = row['userId']
    recomendacoes = row.drop('userId').values
    
    if len(recomendacoes) == 'None':
        return 2
    
    if user_id in df_teste.index:
        for filme in recomendacoes:
            if filme in df_teste.columns and df_teste.loc[user_id, filme] != 0:
                return 1
    
    return 0
# Aplicar a função a cada linha do DataFrame de recomendações
recomendacoes_finais_df['Assistiu'] = recomendacoes_finais_df.apply(assistiu_ou_nao, axis=1, df_teste=recomendacao_genero_teste1)
# Tabela final das recomendações
recomendacoes_finais_df_com_assistiu
userId Recomendacao_genero_1 Recomendacao_genero_2 Recomendacao_filme_1 Recomendacao_filme_2 Recomendacao_filme_3 Assistiu
0 128 War Action Run Silent Run Deep (1958) Greyhound (2020) Matrix, The (1999) 0
1 172 Action Mystery Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) 0
2 465 Romance Adventure Adjustment Bureau, The (2011) Meet Joe Black (1998) Lord of the Rings: The Fellowship of the Ring,... 0
3 598 Action Mystery Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) 1
4 919 Romance Adventure Adjustment Bureau, The (2011) Meet Joe Black (1998) Lord of the Rings: The Fellowship of the Ring,... 1
... ... ... ... ... ... ... ...
1981 330236 Romance Adventure Adjustment Bureau, The (2011) Meet Joe Black (1998) Lord of the Rings: The Fellowship of the Ring,... 0
1982 330321 Action Mystery Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) 0
1983 330496 None None None None None 0
1984 330667 Action Mystery Matrix, The (1999) Fight Club (1999) Seven (a.k.a. Se7en) (1995) 0
1985 330948 Thriller Crime Fugitive, The (1993) Spotlight (2015) Shawshank Redemption, The (1994) 0

1986 rows × 7 columns

# Salvar a tabela
recomendacoes_finais_df_com_assistiu.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/4.Modelagem_FP_Growth/recomendacoes_finais_df_com_assistiu", compression="gzip")
# Avaliar a qualidade das recomendações
recomendacoes_finais_df_com_assistiu[['Assistiu']].value_counts()
Assistiu
0           1599
1            387
Name: count, dtype: int64

O FP-Growth, só conseguiu acertar apenas 19% das recomendações.



Ícone Ver README do projeto